#include <sstream>
#include "xDocument.h"
#include <utility>

namespace {
	class CLock {
		LPCRITICAL_SECTION m_lock;
	public:
		CLock(LPCRITICAL_SECTION lock) 
		    : m_lock(lock)
		{
			assert (m_lock != NULL);
			::EnterCriticalSection(m_lock);
		}

		~CLock() {
			::LeaveCriticalSection(m_lock);
		}
	};
}

#define LOCK()        CLock lock((LPCRITICAL_SECTION)&m_cs)


FileInfo& CxDocument::_GetFileByIndex(INDEX index) {
	assert (index < getFileCount());

	LOCK();
	return m_files.at(index);
}


CxDocument::CxDocument()
	: m_lastID(0)
	, m_err(ERROR_SUCCESS)
{
	::InitializeCriticalSection(&m_cs);
}

CxDocument::~CxDocument() {
	::DeleteCriticalSection(&m_cs);
}


UINT CxDocument::getLastError() {
	return m_err;
}

FileInfo::ID CxDocument::addFile(const TCHAR *pathname) {
	m_err = ERROR_SUCCESS;

	LOCK();
	// check fur duplication
	if (m_nameIdMap.find(pathname) != m_nameIdMap.end()) {
		m_err = ERROR_ALREADY_EXISTS;
		return FileInfo::INVALID_ID;
	}

	// get file attributes (size)
	WIN32_FILE_ATTRIBUTE_DATA wfad;
	if (!GetFileAttributesEx(pathname, GetFileExInfoStandard, &wfad)) {
		// TODO: report error
		return FileInfo::INVALID_ID;
	}

	FileInfo fi;
	fi.id = m_lastID ++;
	fi.pathname = pathname;
	fi.size = wfad.nFileSizeLow;
	fi.status = FileInfo::US_TODO;
	fi.progress = 0;
	fi.info = pathname;

	m_files.push_back(fi);
	m_nameIdMap.insert(std::make_pair(fi.pathname, fi.id));

	// event
	FileInfo::ID id = fi.id;
	_TriggerEvent(EVT_ADD, &id);

	return fi.id;
}

void CxDocument::remove(FileInfo::ID id) {
	FileContainer::iterator it;
	LOCK();
	for (it = m_files.begin(); it != m_files.end(); ++ it) {
		if (it->id == id) {
			break;
		}
	}
	assert (it != m_files.end());

	FileInfo fi = *it;
	tstring pathname = it->pathname;
	m_files.erase(it);
	m_nameIdMap.erase(m_nameIdMap.find(pathname));

	_TriggerEvent(EVT_REMOVE, &fi);
}

void CxDocument::clear() {
	LOCK();
	m_files.clear();
	m_nameIdMap.clear();

	_TriggerEvent(EVT_CLEAR, NULL);
}

void CxDocument::setFileUploadProgress(FileInfo::ID id, size_t progress) {
	INDEX index = getIndexById(id);
	assert (index != INVALID_INDEX);

	LOCK();
	FileInfo &fi = _GetFileByIndex(index);
	assert (progress == 0 || fi.status == FileInfo::US_UPLOADING);
	fi.progress = progress;

	_TriggerEvent(EVT_UPDATE, &id);
}

void CxDocument::setFileUploadResult(FileInfo::ID id, FileInfo::UPLOAD_STATUS status, const std::string &result) {
	INDEX index = getIndexById(id);
	assert (index != INVALID_INDEX);

	LOCK();
	FileInfo &fi = _GetFileByIndex(index);
	fi.progress = 0;
	fi.status = status;
	if (status == FileInfo::US_OK) {
		fi.url = result;
		fi.info = util_mbs2tcs(result);
	} else if (status == FileInfo::US_FAILED) {
		fi.result = result;

		// skip the HTTP HEADERS
		const char *http_response = result.c_str();
		while (strstr(http_response, "HTTP/1.") == http_response) {
			const char *p = strstr(http_response, "\r\n\r\n");
			if (p == NULL) {
				break;
			}
			http_response = p + 4;
		}

		// prepare the info
		std::stringstream ss;
		HtmlDom dom = html_load(http_response);
		HtmlDom::iterator it, end = dom.end();
		for (it = dom.begin(); it != end; ++ it) {
			if ((!it->isTag()) && (!it->isComment())) {
				std::string t = it->text();
				size_t len = t.length();
				bool all_blank = true;
				for (size_t i = 0; i < len; ++ i) {
					char c = t[i];
					if (c > ' ' && c != '\t') {
						all_blank = false;
						break;
					}
				}
				if (!all_blank) {
					ss << t;
				}
			} else {
				const char *tag = it->tagName().c_str();
				static char *filters[] = {
					"script", "link", "style",
				};
				bool found = false;
				for (int i = 0; i < COUNT_OF(filters); ++ i) {
					if (_stricmp(tag, filters[i]) == 0) {
						found = true;
						break;
					}
				}
				if (found) {
					it.skip_children();
				}

			}
		}

		fi.info = util_mbs2tcs(ss.str());
	} else if (status == FileInfo::US_TODO) {// user abort
		fi.info = fi.pathname;
	} else {
		assert (false);
	}

	_TriggerEvent(EVT_UPDATE, &id);
}

FileInfo::ID CxDocument::getUpdateId() {
	LOCK();
	FileContainer::iterator it;
	INDEX index = 0;
	for (it = m_files.begin(); it != m_files.end(); ++ it, ++ index) {
		if (it->status == FileInfo::US_TODO) {
			it->status = FileInfo::US_UPLOADING;
			it->progress = 0;

			FileInfo::ID id = it->id;
			_TriggerEvent(EVT_UPDATE, &id);

			return it->id;
		}
	}

	return FileInfo::INVALID_ID;
}


CxDocument::INDEX CxDocument::getIndexById(FileInfo::ID id) const {
	LOCK();
	FileContainer::const_iterator it;
	CxDocument::INDEX index = 0;
	for (it = m_files.begin(); it != m_files.end(); ++ it, ++ index) {
		if (it->id == id) {
			return index;
		}
	}

	return INVALID_INDEX;
}

size_t CxDocument::getFileCount() const {
	LOCK();
	return m_files.size();
}

const FileInfo& CxDocument::getFileByIndex(CxDocument::INDEX index) const {
	assert (index < getFileCount());

	LOCK();
	return m_files.at(index);
}

size_t CxDocument::getFileCountByStatus(FileInfo::UPLOAD_STATUS status) const {
	LOCK();
	FileContainer::const_iterator it;
	size_t count = 0;
	for (it = m_files.begin(); it != m_files.end(); ++ it) {
		if (it->status == status) {
			count ++;
		}
	}

	return count;
}

CxDocument::INDEX CxDocument::getFirstIndexByStatus(FileInfo::UPLOAD_STATUS status) const {
	LOCK();
	FileContainer::const_iterator it;
	INDEX index = 0;
	for (it = m_files.begin(); it != m_files.end(); ++ it, ++ index) {
		if (it->status == status) {
			return index;
		}
	}

	return INVALID_INDEX;
}


